#pragma once

#include <vex/iterable/range_state.h>
#include <vex/iterable/stl_interop/convert_traits.h>

namespace vex { namespace iterable { namespace stl_interop {

    /// <summary>
    /// Adaptor body for pair of stl iterators. Wraps iterator range and provides member
    /// functions expected from an iterator. Used as internal implementation for stl_range_from_iterators
    /// </summary>
    template< class TItr, class TCategory, class TRef >
    class range_from_iterator_adapter_body {
        TItr m_beg, m_end;
        range_state m_state;
    public:
        template< class UItr >
        range_from_iterator_adapter_body(
            UItr && p_beg, 
            UItr && p_end, 
            range_state const & p_state = range_state::not_started)
            : m_beg(std::forward<UItr>(p_beg))
            , m_end(std::forward<UItr>(p_end))
            , m_state(p_state) {}

        template< class UItr, class UCategory, class URef >
        range_from_iterator_adapter_body(
            range_from_iterator_adapter_body<UItr, UCategory, URef> const & p_other)
            : m_beg(std::forward<UItr>(p_other.m_beg))
            , m_end(std::forward<UItr>(p_other.m_end))
            , m_state(p_other.m_state) {}

        range_from_iterator_adapter_body(range_from_iterator_adapter_body&& p_other)
            : m_beg(std::move(p_other.m_beg))
            , m_end(std::move(p_other.m_end))
            , m_state(std::move(p_other.m_state)) {}

        range_from_iterator_adapter_body& operator = (range_from_iterator_adapter_body&& p_other)
        {
            range_from_iterator_adapter_body(std::move(p_other)).swap(this);
            return *this;
        }

        void swap(range_from_iterator_adapter_body& p_other)
        {
            std::swap(m_beg, p_other.m_beg);
            std::swap(m_end, p_other.m_end);
            std::swap(m_state, p_other.m_state);
        }

        bool move_front()
        {
            if (m_state == range_state::started) {
                ++m_beg;
            }

            m_state = m_beg != m_end ? range_state::started : range_state::ended;

            return m_state == range_state::started;
        }

        typename TRef front() const
        {
#ifdef _DEBUG
            if (range_state::started != this->m_state) {
                throw std::runtime_error("Range is not started!");
            }
#endif
            assert(range_state::started == this->m_state);
            return *this->m_beg;
        }

        range_from_iterator_adapter_body save() const
        {
            return *this;
        }

        bool move_back()
        {
            if (this->m_state == range_state::started) {
                --this->m_end;
            }

            this->m_state = this->m_beg != this->m_end ? range_state::started : range_state::ended;

            return this->m_state == range_state::started;
        }

        typename TRef back() const
        {
#ifdef _DEBUG
            if (range_state::started != this->m_state) {
                throw std::runtime_error("Range is not started!");
            }
#endif
            assert(range_state::started == this->m_state);
            auto t_tmp = this->m_end;
            return *(--t_tmp);
        }

        typename TRef at(std::size_t p_diff) const
        {
#ifdef _DEBUG
            if (!(std::ptrdiff_t(p_diff) < std::distance(this->m_beg, this->m_end))) {
                throw std::runtime_error("Index is out of iterable!");
            }
#endif
            assert(std::ptrdiff_t(p_diff) < std::distance(this->m_beg, this->m_end));
            return *(this->m_beg + p_diff);
        }

        /// Move iterable front of N positions forward
        // move_front_of(N) is defined to have the same effect as calling move_front N times
        bool move_front_of(std::size_t p_diff)
        {                    
            if (this->m_state == range_state::not_started) {
                if (p_diff == 0) 
                    return false;
                else
                    --p_diff;
            }

            this->m_beg = p_diff < static_cast<std::size_t>(this->m_end - this->m_beg) ?
                this->m_beg + p_diff : this->m_end;

            this->m_state = this->m_beg == this->m_end ? range_state::ended : range_state::started;

            return this->m_state == range_state::started;
        }

        /// Move iterable back of N positions backward
        // move_back_of(N) is defined to have the same effect as calling move_back N times
        bool move_back_of(std::size_t p_diff)
        {
            if (this->m_state == range_state::not_started) {
                if (p_diff == 0)
                    return false;
                else
                    --p_diff;
            }

            this->m_end = p_diff <= static_cast<std::size_t>(this->m_end - this->m_beg) ?
                this->m_end - p_diff : this->m_beg;

            this->m_state = this->m_beg == this->m_end ? range_state::ended : range_state::started;

            return this->m_state == range_state::started;
        }

        std::size_t size() const
        {
            return std::distance(this->m_beg, this->m_end);
        }
    };

    /// Specialization for output ranges. No need to store state and end iterator.
    template< class TItr, class URef>
    class range_from_iterator_adapter_body<TItr, output_range_tag, URef> {
        TItr m_current;
    public:
        template< class UItr >
        range_from_iterator_adapter_body(UItr&& p_beg, std::output_iterator_tag const&)
            : m_current(std::forward<UItr>(p_beg)) {}

        template< class UItr, class URef >
        range_from_iterator_adapter_body(
            range_from_iterator_adapter_body<UItr, output_range_tag, URef> const & p_other)
            : m_current(std::forward<UItr>(p_other.m_current)) {}

        range_from_iterator_adapter_body(range_from_iterator_adapter_body&& p_other)
            : m_current(std::move(p_other.m_current)) {}

        range_from_iterator_adapter_body& operator = (range_from_iterator_adapter_body&& p_other)
        {
            range_from_iterator_adapter_body(std::move(p_other)).swap(*this);
            return *this;
        }

        void swap(range_from_iterator_adapter_body& p_other)
        {
            std::swap(m_current, p_other.m_current);
        }

        bool move_front()
        {
            return true;
        }

        template <class UValue>
        bool put(UValue && p_value)
        {
            *m_current++ = std::forward<UValue>(p_value);
            return true;
        }
    };
}}}